import { AxiosPromise } from 'axios'
import request from '@/utils/request'
import { IndexedDB, STORE } from './indexedDB'

/**
 * 抽象分页
 */
export interface IPage {
  readonly current: number // 当前页码
  readonly pages: number // 总页数
  readonly size: number // 每页记录数
  readonly total: number // 总记录数
}

/**
 * 抽象响应
 */
export interface IResponse<T> {
  readonly code: string // 响应状态码
  readonly data: T // 响应数据（实体/实体列表/分页实体列表）
  readonly handleTime: number // 响应时间戳
  readonly message: string // 响应提示信息
}

export interface IModelParams {
  getAll?: string // 获取全部数据接口，不分页
  getPage?: string // 获取分页数据
  get?: string // 获取列表详情
  del?: string // 列表数据编辑
  add?: string // 列表数据新增
  update?: string // 列表数据更新
}
/**
 * Ajax请求执行器
 */
export class Request<T> {
  private _loading: boolean = false // 执行状态
  private _data: IResponse<T> = {} as IResponse<T> // 响应数据
  private _url: string = '' // 请求接口
  private _method: string = 'get' // 请求类型
  private _cache: boolean = false // 缓存时间 0为不缓存，单位为h
  private _expire: number = 0 // 过期时间，单位h，若为0则每次都进行数据更新
  private _version: string = process.env.VUE_APP_VERSION as string
  // 记录尚未响应的请求(减少重复请求)
  private static _requestings: {
    [propName: string]: AxiosPromise<IResponse<any>>
  } = {}

  get loading() {
    return this._loading
  }

  get executor(): Function {
    return (params: any = {}): AxiosPromise<IResponse<any>> =>
      request({
        url: this._url,
        method: this._method,
        [this._method === 'get' ? 'params' : 'data']: params
      })
  }

  get data() {
    return this._data || {} as IResponse<T>
  }

  get DB() {
    return IndexedDB.getInstance()
  }

  get expireTime() {
    return this._expire * 60 * 60 * 1000
  }

  /**
   * 设置是否需要缓存
   * @param isCache 启用缓存(Boolean)/缓存时间(单位h)
   */
  public cache(isCache: boolean | number = true) {
    if (typeof isCache === 'boolean') {
      this._cache = isCache
      this._expire = 0
    } else {
      this._cache = true
      this._expire = isCache
    }
  }

  /**
   * 设置请求参数
   * @param url 接口地址
   * @param method 类型
   */
  public setExecutor(url: string, method: string = 'get'): Request<T> {
    this._url = url
    this._method = method
    return this
  }

  /**
   * 发送请求
   * @param params 请求参数
   */
  public async send(params?: any): Promise<IResponse<T>> {
    // 判断是否存在加载函数，不存在则中断操作
    if (!this._url) {
      throw new Error('操作失败，尚未设置请求接口！')
    }
    // 缓存主键
    const url = `${this._method}@${this._url}#${JSON.stringify(params)}`
    // 设置加载状态
    this._loading = true
    // 读取缓存
    const cache = this.getCache(url)
    // 请求
    let result: any
    // 过期策略
    if (this._expire > 0) {
      const data = await cache
      // 若缓存中存在有效的数据（数据内容不为空）
      if (data && data.data && (data.data as any).length && this._version === process.env.VUE_APP_VERSION) {
        // 数据的年龄（毫秒）
        const diffTime: number = new Date().getTime() - data.handleTime
        // 若数据未过期，则不需要更新
        if (this.expireTime > diffTime) {
          result = data
        }
      }
    }
    if (!result) {
      // 若请求记录中存在待响应的请求，则无需新增请求
      result = Request._requestings[url] || this.executor(params)
    }
    if (!(result instanceof Promise)) {
      this._loading = false
      this._data = result
      return result
    }
    // 记录请求
    Request._requestings[url] = result
    // 是否需要缓存
    if (this._cache) {
      const data: IResponse<T> = await Promise.race([cache, result])
      if (data) {
        this._loading = false
        this._data = data
      }
    }
    // 无论是否需要缓存，都需更新数据（有设置过期时间除外）
    const data: IResponse<T> = await result.finally(() => {
      delete Request._requestings[url]
      this._loading = false
    })
    this._data = data
    // 若需要缓存，则将结果存库处理
    if (this._cache) {
      const obj = {
        url,
        data: data,
        version: process.env.VUE_APP_VERSION
      }
      this.DB.updateDataByKey(STORE.REQUEST, obj)
    }
    return this._data
  }

  /**
   * 获取缓存
   * @param key 缓存key值
   */
  private async getCache(key: string): Promise<IResponse<T>> {
    // 读取缓存
    const result: { data: IResponse<T>, version: string } = await this.DB.getDataByKey(STORE.REQUEST, key) || {}
    this._version = result.version
    return result.data
  }

  constructor(url: string = '', method: string = 'get') {
    if (!url) return
    this._url = url
    this._method = method
  }
}

/**
 * 基础请求
 */
export class BaseRequest<T> {
  private _params: any // 请求参数
  private _queryParams: any // 常变参数
  private _request: Request<T> = new Request<T>() // 请求模型对象

  get data(): T {
    return (this._request.data || {} as any).data
  }

  /**
   * 请求参数
   */
  get params() {
    return Object.assign({}, this._params, this._queryParams)
  }

  /**
   * 加载状态
   */
  get loading() {
    return this._request.loading
  }

  /**
   * 设置请求参数（固定参数）
   * @param params 查询参数
   */
  public setRequestParams(params: any) {
    this._params = params
    this._queryParams = {}
    return this
  }

  /**
   * 设置查询参数（常变参数）
   * @param params 查询参数
   */
  public setQueryParams(params: any) {
    this._queryParams = params
    return this
  }

  /**
   * 重新请求
   */
  public refresh() {
    return this.send()
  }

  /**
   * 请求方法
   * @param params 请求参数
   */
  public async send(params = this._queryParams) {
    this._queryParams = Object.assign({}, params)
    await this._request.send(this.params)
    return this._request.data
  }

  /**
   * 设置是否需要缓存
   * @param isCache 启用缓存/缓存时间
   */
  public cache(isCache: boolean | number = true) {
    this._request.cache(isCache)
    return this
  }

  /**
   * 构造器
   * @param executor 请求执行器
   */
  constructor(url?: string, method: string = 'get') {
    this._request = new Request<T>(url, method)
  }
}

/**
 * 实体操作类
 */
export class Entity<T> {
  private _data: T = {} as T
  private _add: BaseRequest<T> = new BaseRequest<T>() // 增
  private _del: BaseRequest<T> = new BaseRequest<T>() // 删
  private _update: BaseRequest<T> = new BaseRequest<T>() // 改
  private _get: BaseRequest<T> = new BaseRequest<T>() // 查
  private _default: T = {} as T

  get data() {
    return this._data || {} as T
  }

  get loading() {
    return this._get.loading || this._del.loading || this._add.loading || this._update.loading || false
  }

  get default() {
    return Object.assign({}, this._default)
  }

  public setCurrentRow(value: T): Entity<T> {
    this._data = Object.assign({}, value)
    this._add.setRequestParams(this._data)
    this._update.setRequestParams(this._data)
    return this
  }

  /**
   * 查询一条记录
   * @param params 查询条件
   */
  public async get(config?: string | number | { id: string | number }): Promise<T> {
    const type = Object.prototype.toString.call(config)
    let params: any
    if (['[object String]', '[object Number]'].includes(type)) {
      params = {
        id: config
      }
    } else if (type === '[object Object]') {
      params = config
    }
    if (params) {
      const data = await this._get.send(params)
      return this.setCurrentRow(data.data).data
    }
    return this.create()
  }

  /**
   * 删除数据
   * @param params 可选，删除条件，默认是传id对象
   */
  public del(params: { id: string | number } = { id: (this.data as any).id }): Promise<IResponse<T>> {
    return this._del.send(params)
  }

  /**
   * 保存数据
   */
  public save(): Promise<IResponse<T>> {
    if ((this.data as any).id) {
      return this.update()
    }
    return this.add()
  }

  /**
   * 插入数据
   */
  public add(): Promise<IResponse<T>> {
    return this._add.send(this.data)
  }

  /**
   * 更新数据
   */
  public update(): Promise<IResponse<T>> {
    return this._update.send(this.data)
  }

  /**
   * 创建数据对象
   * @param params 创建时的默认值
   */
  public create() {
    this.setCurrentRow(this.default)
    return this.data
  }

  /**
   * 设置默认值
   * @param data 默认值
   */
  public setDefault(data: T) {
    this._default = data
    return this
  }

  constructor({ get, del, add, update }: IModelParams = {}) {
    this._get = new BaseRequest<T>(get, 'get')
    this._del = new BaseRequest<T>(del, 'post')
    this._add = new BaseRequest<T>(add, 'post')
    this._update = new BaseRequest<T>(update, 'post')
    this.setCurrentRow(this.data)
  }
}

export class CurdRequest<T, U> extends BaseRequest<U> {
  private _currentRow: Entity<T> = new Entity<T>() // 当前行

  /**
   * 当前行
   */
  get currentRow() {
    return this._currentRow
  }

  /**
   * 设置当前行
   * @param value 当前行实体
   */
  public setCurrentRow(value: T): Entity<T> {
    this._currentRow.setCurrentRow(value)
    return this.currentRow
  }

  /**
   * 追加空记录
   */
  public addRow(): Entity<T> {
    this.currentRow.create()
    return this.currentRow
  }

  /**
   * 设置模型默认值
   * @param data 默认值
   */
  public default(data: T) {
    this._currentRow.setDefault(data)
    return this
  }

  /**
   * 注册curd模型
   * @param params 接口列表
   * @param defaultData 模型默认值
   */
  protected registerEntity(params: IModelParams, defaultData?: any) {
    this._currentRow = new Entity<T>(params).setDefault(defaultData)
    return this
  }
}

/**
 * 基础列表
 */
export class BaseList<T, U> extends CurdRequest<T, U> {
  private _format?: Function // 格式化函数
  private _records: any[] = [] // 格式化后的结果集

  // 处理过的记录集
  get records(): T[] {
    return this._records && this._records.length > 0 ? this._records : this.sources
  }

  // 源记录集
  get sources() {
    const data: any = super.data
    if (data instanceof Array) {
      return data
    } else if (data instanceof Object) {
      return data.records || data
    }
    return data || []
  }

  /**
   * 查询数据
   * @param params 查询条件
   */
  public async query(params?: any) {
    const data = await super.send(params)
    if (this._format) {
      this._records = this.sources.map((d: any) => {
        // 暂时没有进行深拷贝操作，后续扩展
        const m = Object.assign({}, d)
        return (this._format && this._format(m)) || m
      })
    }
    return data
  }

  /**
   * 设置格式化函数
   * @param format 格式化回调函数
   */
  public format(format: Function) {
    this._format = format
    return this
  }
}

/**
 * 不分页列表
 */
export class List<T> extends BaseList<T, T[]> {
  constructor(params: IModelParams = {}) {
    super(params.getAll)
    super.registerEntity(params)
  }
}

/**
 * 分页列表
 */
export class Page<T> extends BaseList<T, Page<T>> implements IPage {
  private _sizes: number[] = [15, 20, 30, 50, 100] // 可选页大小集合
  private _current: number = 1 // 当前页码
  private _size: number = this._sizes[0] // 当前页大小

  /**
   * 响应内容结果
   */
  get data(): Page<T> {
    return super.data || {} as Page<T>
  }

  /**
   * 当前页码
   */
  get current(): number {
    return this.data.current || 0
  }

  /**
   * 页大小
   */
  get size(): number {
    return this.data.size || this._sizes[0]
  }

  /**
   * 总页数
   */
  get pages(): number {
    return this.data.pages
  }

  /**
   * 总记录数
   */
  get total(): number {
    return this.data.total
  }

  /**
   * 可选页大小
   */
  get sizes() {
    return this._sizes
  }

  /**
   * 已处理的请求参数
   */
  get params() {
    const params: any = {
      ...super.params,
      current: this._current,
      size: this._size
    }
    return params
  }

  /**
   * 修改当前页码
   * @param current 当前页码
   */
  public async setCurrent(current: number = 1): Promise<IResponse<Page<T>>> {
    this._current = current
    const data = await super.query(this.params)
    if (current > this.pages) {
      return this.setCurrent(this.pages)
    }
    return data
  }

  /**
   * 刷新当前页
   */
  public refresh(): Promise<IResponse<Page<T>>> {
    return this.setCurrent(this.current)
  }

  /**
   * 修改当前页大小
   * @param size 当前页大小
   */
  public setSize(size: number = this._sizes[0]): Promise<IResponse<Page<T>>> {
    // 计算出当前分页下第一条数据的序号
    const oldNumber = this.size * (this._current - 1) + 1
    this._size = size
    // 计算修改页大小后当前对应的页码(相对于第一条记录)
    const current = Math.ceil(oldNumber / size)
    return this.setCurrent(current)
  }

  /**
   * 设置可选页大小集合
   * @param sizes 可选页大小集合
   */
  public setSizes(sizes: number[] = this.sizes): Page<T> {
    this._sizes = sizes
    return this
  }

  constructor(params: IModelParams = {}, sizes?: number[]) {
    super(params.getPage)
    super.registerEntity(params)
    this.setSizes(sizes)
  }
}
